#include "gui.h"
#include "db/dbc.h"
#include "Config/Config.hpp"
#include "ConfigGui/ConfigGui.hpp"
#include "PasswordForm/PasswordForm.hpp"

#include <vector>

// public:
Gui::Gui(QWidget *parent)/*{{{*/
: QWidget(parent)
{
  this->createActions();
  this->createTrayIcon();

  this->timer = new QTimer(this);
  connect(this->timer, SIGNAL(timeout()),
          this, SLOT(dbOperation()));

  this->itxt = new QTextEdit;
  this->itxt->setReadOnly(true);

  // FIXME: make a menu in place of buttons
  this->startBtn = new QPushButton(tr("Start"), this);
  this->stopBtn = new QPushButton(tr("Stop"), this);
  this->restartBtn = new QPushButton(tr("Restart"), this);
  this->confBtn = new QPushButton(tr("Configuration"), this);
  this->quitBtn = new QPushButton(tr("Quit"), this);

  connect(this->startBtn, SIGNAL(clicked()),
          this, SLOT(start()));
  connect(this->stopBtn, SIGNAL(clicked()),
          this, SLOT(stop()));
  connect(this->restartBtn, SIGNAL(clicked()),
          this, SLOT(restart()));
  connect(this->confBtn, SIGNAL(clicked()),
          this, SLOT(configure()));
  connect(this->quitBtn, SIGNAL(clicked()),
          qApp, SLOT(quit()));

  QHBoxLayout *layer = new QHBoxLayout();
  layer->addWidget(startBtn);
  layer->addWidget(stopBtn);
  layer->addWidget(restartBtn);
  layer->addWidget(confBtn);
  layer->addWidget(quitBtn);

  this->lay = new QVBoxLayout();
  this->lay->addItem(layer);
  this->lay->addWidget(this->itxt);
  this->setLayout(this->lay);

  this->show();
  this->restartBtn->click();
}/*}}}*/

Gui::~Gui()/*{{{*/
{
  delete this->timer;
  delete this->itxt;
}/*}}}*/


bool Gui::init()/*{{{*/
{
  Config confReader;

  if (!confReader.parseFile("DBTransfer.conf"))
  {
    //FIXME:
    qDebug() << __FILE__ << ' ' << __LINE__ << ": error while file parsing!";
    return false;
  }
  this->configLocalDBConnection   = confReader.getValue(QString("local_db_connection"));
  this->configRemoteDBConnection  = confReader.getValue(QString("remote_db_connection"));
  this->configRemoteDBEncoding    = confReader.getValue(QString("remote_db_encoding"));
  this->configSqlReplicTableName  = confReader.getValue(QString("sql_replic_table_name"));
  this->configRequestDelay        = confReader.getValue(QString("request_delay"));
  // set delay
  sscanf(this->configRequestDelay.toStdString().c_str(), "%d", &this->delay);
  this->delay *= 1000; // to make delay from seconds to microseconds

  PasswordForm *pass = new PasswordForm();
  if (pass->exec() != QDialog::Accepted)
  {
    return false;
  }

  QUrl url;
  url = this->configLocalDBConnection;
  url.setPassword(pass->getLocalPassword());
  this->configLocalDBConnection = url.toString();

  url = this->configRemoteDBConnection;
  url.setPassword(pass->getRemotePassword());
  this->configRemoteDBConnection = url.toString();

  delete pass;
  return true;
}/*}}}*/

DBConnect * Gui::createDBConnection(QString initString)/*{{{*/
{
  QUrl url = initString;

  if (!url.isValid())
  {
    //FIXME:
    qDebug() << "wrong db connection property!";
    exit(1);
  }

  DBConnect *local = new DBConnect(url.scheme(),
                                   url.path().remove(0, 1),
                                   url.host(),
                                   url.userName(),
                                   url.password());
  if (!local->status)
  {
    //FIXME:
    this->itxt->append(QString("Cannot connect to ") + url.scheme());
  }
  else
  {
    this->itxt->append(QString("Connect to ")+ url.scheme() + QString(" OK!"));
  }
  return local;
}/*}}}*/

int Gui::vectorQStringSize(std::vector<QString> v)/*{{{*/
{
  int size = 0;
  // FIXME: make an iterator work
  for (unsigned int i = 0; i < v.size(); ++i)
  {
    size += v[i].size() + 1;
  }
  return size + 1;
}/*}}}*/

void Gui::setEncoding(QString sqlConnectionType, QString sqlEncoding, std::vector<QString> &v)/*{{{*/
{
  QString sqlStr;
  if (sqlConnectionType == "QPSQL")
  {
    sqlStr = "SET client_encoding = '";
    sqlStr += sqlEncoding + "';";
    v.push_back(sqlStr);
  }
  else if (sqlConnectionType == "QMYSQL")
  {
    sqlStr = "SET NAMES '" + sqlEncoding + "';";
    v.push_back(sqlStr);
    sqlStr = "SET CHARACTER SET '" + sqlEncoding + "';";
    v.push_back(sqlStr);
  }
  else if (sqlConnectionType == "QIBASE")
  {
    sqlStr = "SET NAMES " + sqlEncoding + ";";
    v.push_back(sqlStr);
  }
  else
  {
    qDebug() << "setEncoding(): wrong sql connection type!";
    exit(1);
  }
}/*}}}*/

void Gui::vectorQString2char(char *array, std::vector<QString> v)/*{{{*/
{
  int counter = 0;
  for (unsigned int i = 0; i < v.size(); ++i)
  {
    for (int j = 0; j < v[i].size(); ++j)
    {
      array[counter++] = v[i].toAscii()[j];
    }
    array[counter++] = '\n';
  }
  array[counter] = '\0';
}/*}}}*/

bool Gui::setReplicData(DBConnect *connection, std::vector<QString> &sqlString)/*{{{*/
{
  //checks
  if (!connection->isOpen())
  {
    qDebug() << "setReplicData(): Database connection is closed, cannot execute query!";
    return false;
  }

  for(std::vector<QString>::iterator i = sqlString.begin(); i != sqlString.end(); ++i)
  {
    this->itxt->append(*i);
    // qDebug() << sqlString[i];
    //FIXME: uncomment next line
    //connection->execQuery(*i);
  }
  this->itxt->append(QString("Inserting in remote DBMS!"));

  return true;
}/*}}}*/

void Gui::getReplicData(DBConnect *connection, QString tableName, std::vector<QString> &sqlString)/*{{{*/
{
  // Variable definitions
  QString queryStr;
  QString sqlStr, primaryFieldName, primaryFieldValue;
  std::vector< QString > sqlFieldList;
  QString fieldList;
  QString sqlOperationType;
  QString sqlPrevOperationType;
  QString sqlTableName;
  QString sqlFieldName;
  QString sqlFieldType;
  QString primaryPrevFieldName;
  QString sqlFieldValue;
  QString primaryPrevFieldValue;
  QString sqlFieldID;
  int previd = -1;
  int id = -1;
  bool was_cycle = false;

  // checks
  if (!connection->isOpen())
  {
    qDebug() << "getReplicData(): database connection is closed, cannot execute query!";
    return;
  }
  if (tableName.isEmpty())
  {
    qDebug() << "getReplicData(): table name is empty! Config file is possibly wrong!";
    return;
  }

  //Get Data from local queue table
  queryStr = "SELECT * FROM " + tableName + ";";
  connection->execQuery(queryStr);

  while (connection->query->next())
  {
    was_cycle = true;
    // get new variables
    // save previous variables
    previd = id;
    sqlPrevOperationType = sqlOperationType;

    id                = connection->query->value(0).toInt();
    sqlOperationType  = connection->query->value(1).toString();
    sqlTableName      = connection->query->value(2).toString();
    sqlFieldName      = connection->query->value(3).toString();
    sqlFieldType      = connection->query->value(4).toString();
    sqlFieldValue     = connection->query->value(5).toString();

    // if we reached another id
    if (id != previd)
    {
      // string finish
      if (previd != -1)
      {
        switch(sqlPrevOperationType.toAscii()[0])
        {
          case INSERT_OPER:
            fieldList = "(";
            for (unsigned int i = 0; i < sqlFieldList.size(); ++i)
            {
              if (i != 0)
              {
                fieldList += ", ";
              }
              fieldList += sqlFieldList[i];
            }
            fieldList += ")";

            sqlStr = "INSERT INTO " + sqlTableName + fieldList + " VALUES (" + sqlStr + ")";
            
            break;
          case UPDATE_OPER:
            sqlStr += " WHERE " + primaryFieldName + " = '" + primaryFieldValue + "'";
            break;
          case DELETE_OPER:
            sqlStr += " WHERE " + primaryFieldName + " = '" + primaryFieldValue + "'";
            break;

          default:
            throw new QString("wrong type!");
        }
        sqlStr += ";";
        sqlString.push_back(sqlStr);
      }

      // string start
      switch(sqlOperationType.toAscii()[0])
      {
        case INSERT_OPER:
          //sqlStr = "INSERT INTO " + sqlTableName + " VALUES (";
          sqlFieldList.clear();
          break;
        case UPDATE_OPER:
          sqlStr = "UPDATE " + sqlTableName + " SET ";
          break;
        case DELETE_OPER:
          sqlStr = "DELETE FROM " + sqlTableName;
          break;

        default:
          throw new QString("wrong type!");
      }
    }

    // remember primary name an value
    if (sqlFieldType == "y")
    {
      primaryFieldName = sqlFieldName;
      primaryFieldValue = sqlFieldValue;
    }

    // string center
    switch (sqlOperationType.toAscii()[0])
    {
      case INSERT_OPER:
        if (previd == id)
        {
          sqlStr += ", ";
        }
        sqlStr += "'" + sqlFieldValue + "'";

        sqlFieldList.push_back(sqlFieldName);
        break;
      case UPDATE_OPER:
        if (previd == id)
        {
          sqlStr += ", ";
        }
        sqlStr += sqlFieldName + " = '" + sqlFieldValue + "'";
        break;
      case DELETE_OPER:
        // do nothing
        break;

      default:
        throw new QString("wrong type!");
    }
  } //while

  // if we inserted any line
  if (was_cycle)
  {
    // string finish
    switch(sqlOperationType.toAscii()[0])
    {
      case INSERT_OPER:
        fieldList = "(";
        for (unsigned int i = 0; i < sqlFieldList.size(); ++i)
        {
          if (i != 0)
          {
            fieldList += ", ";
          }
          fieldList += sqlFieldList[i];
        }
        fieldList += ")";

        sqlStr = "INSERT INTO " + sqlTableName + fieldList + " VALUES (" + sqlStr + ")";

        break;
      case UPDATE_OPER:
        sqlStr += " WHERE " + primaryFieldName + " = '" + primaryFieldValue + "'";
        break;
      case DELETE_OPER:
        sqlStr += " WHERE " + primaryFieldName + " = '" + primaryFieldValue + "'";
        break;

      default:
        throw new QString("wrong type!");
    }
    sqlStr += ";";
    sqlString.push_back(sqlStr);
  }
}/*}}}*/

void Gui::createActions()/*{{{*/
{
  this->startAction = new QAction(tr("Start"), this);
  connect(this->startAction, SIGNAL(triggered()),
          this, SLOT(start()));

  this->stopAction = new QAction(tr("Stop"), this);
  connect(this->stopAction, SIGNAL(triggered()),
          this, SLOT(stop()));

  this->restartAction = new QAction(tr("Restart"), this);
  connect(this->restartAction, SIGNAL(triggered()),
          this, SLOT(restart()));

  this->quitAction = new QAction(tr("Quit"), this);
  connect(this->quitAction, SIGNAL(triggered()),
          qApp, SLOT(quit()));
}/*}}}*/

void Gui::createTrayIcon()/*{{{*/
{
  this->trayMenu = new QMenu(this);
  this->trayMenu->addAction(this->startAction);
  this->trayMenu->addAction(this->stopAction);
  this->trayMenu->addAction(this->restartAction);
  this->trayMenu->addSeparator();
  this->trayMenu->addAction(this->quitAction);

  this->tray = new QSystemTrayIcon(this);
  connect(this->tray, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
          this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
  this->tray->setContextMenu(this->trayMenu);
  this->tray->setIcon(QIcon("icons/Default.ico"));
  this->tray->setVisible(true);
}/*}}}*/

// private slots:
void Gui::start()/*{{{*/
{
  if (this->timer->isActive())
  {
    return;
  }
  this->itxt->append(QString(tr("Started")));
  QTimer::singleShot(1, this, SLOT(dbOperation()));
  this->timer->start(this->delay);
}/*}}}*/

void Gui::stop()/*{{{*/
{
  if (!this->timer->isActive())
  {
    return;
  }


  // another level of security! :)
  if (this->localDBConnection != NULL)
  {
    // FIXME
    qDebug() << "warning: connection is closed in stop() function!";
    this->localDBConnection->closeConnection();
    delete this->localDBConnection;
    this->localDBConnection = NULL;
  }
  if (this->remoteDBConnection != NULL)
  {
    qDebug() << "warning: connection is closed in stop() function!";
    this->remoteDBConnection->closeConnection();
    delete this->remoteDBConnection;
    this->remoteDBConnection = NULL;
  }

  this->itxt->append(QString(tr("Stopped")));
  this->timer->stop();

  this->tray->setIcon(QIcon("icons/Default.ico"));
}/*}}}*/

void Gui::restart()/*{{{*/
{
  this->stopBtn->click();

  if (!this->init())
  {
    this->tray->showMessage("Error", "connection wasn`t configured correctly!");
    return;
  }

  this->startBtn->click();
}/*}}}*/

void Gui::configure()/*{{{*/
{
    ConfigGui *configWindow = new ConfigGui("DBTransfer.conf");
    configWindow->resize(800, 600);
    configWindow->exec();
    delete configWindow;
}/*}}}*/

void Gui::setIcon(int)/*{{{*/
{
}/*}}}*/

void Gui::dbOperation()/*{{{*/
{
  this->itxt->append(tr("Time: ") + QTime::currentTime().toString());
  // every single period connect to databases
  this->localDBConnection = this->createDBConnection(this->configLocalDBConnection);
  this->remoteDBConnection = this->createDBConnection(this->configRemoteDBConnection);

  // if one of the connections is lost
  if (!this->localDBConnection->isOpen() || !this->remoteDBConnection->isOpen())
  {
    if (this->localDBConnection->isOpen())
    {
      this->localDBConnection->closeConnection();
    }
    if (this->remoteDBConnection->isOpen())
    {
      this->remoteDBConnection->closeConnection();
    }

    delete this->localDBConnection;
    delete this->remoteDBConnection;
    this->localDBConnection = NULL;
    this->remoteDBConnection = NULL;
    this->tray->setIcon(QIcon("icons/Down.ico"));
    // FIXME: show error message
    return;
  }

  this->tray->setIcon(QIcon("icons/Up.ico"));

  // Set Default encoding for remote DB
  this->setEncoding(remoteDBConnection->dbms, this->configRemoteDBEncoding, this->sqlString);

  // get replic data from local DB
  this->getReplicData(localDBConnection, this->configSqlReplicTableName, this->sqlString);

  if (setReplicData(remoteDBConnection, this->sqlString))
  {
    // if successfull transfer, clear replic table
    QString sqlStr = "DELETE FROM " + this->configSqlReplicTableName + ";";
    this->itxt->append(QString(__FILE__) + QString(" All data was get from local replic table. Must be cleaned replic table!"));
    // FIXME: after all work, uncomment next string
    //localDBConnection->execQuery(sqlStr);
  }

  this->sqlString.clear();
  delete this->localDBConnection;
  delete this->remoteDBConnection;
  this->localDBConnection = NULL;
  this->remoteDBConnection = NULL;
}/*}}}*/

void Gui::closeEvent(QCloseEvent *event)/*{{{*/
{
  this->hide();
  event->ignore();
}/*}}}*/

void Gui::iconActivated(QSystemTrayIcon::ActivationReason reason)/*{{{*/
{
   switch (reason) {
     case QSystemTrayIcon::Trigger:
       this->setVisible(!this->isVisible());
       //this->showNormal();
       break;
    case QSystemTrayIcon::MiddleClick:
       //showMessage();
       break;
     default:
       ;
     }
 }/*}}}*/

bool Gui::event(QEvent *e)/*{{{*/
{
  if (e->type() == QEvent::WindowStateChange && this->isMinimized())
  {
     // FIXME: make a correct window minimizing
  }

  return QWidget::event(e);
}/*}}}*/

